﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using Microsoft.Practices.ServiceLocation;
using SkeletonNET.Base.Crud.UnitOfWork;
using SkeletonNET.Crud.KeyCache;

namespace SkeletonNET.Crud.Repository
{
    /// <summary>
    /// This is a basic implementation of the IRepository interface.
    /// Repository class should be the base of ALL repositories!
    /// </summary>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    /// <typeparam name="TContext">The type of the context.</typeparam>
    public class Repository<TEntity> : IRepository<TEntity>
        where TEntity : class, IEntity, new()
    {
        private IUnitOfWork unitOfWork;
        private bool disposed;
        private IEntityKeyCache keyCache;

        public virtual IUnitOfWork UnitOfWork
        {
            private get { return unitOfWork = unitOfWork ?? ServiceLocator.Current.GetInstance<IUnitOfWork>(); }
            set { unitOfWork = value; }
        }

        protected virtual IEntityKeyCache KeyCache
        {
            get { return keyCache = keyCache ?? ServiceLocator.Current.GetInstance<IEntityKeyCache>(); }
        }

        /// <summary>
        /// Insert a new entity
        /// </summary>
        /// <param name="entity">The entity to insert.</param>
        /// <returns>Reference to the added entity.</returns>
        public virtual TEntity Insert(TEntity entity)
        {
            return UnitOfWork.Add<TEntity>(entity);
        }

        /// <summary>
        /// Delete an existing entity
        /// </summary>
        /// <param name="entity">The entity to delete</param>
        public virtual void Delete(TEntity entity)
        {
            UnitOfWork.Remove<TEntity>(entity);
        }

        /// <summary>
        /// Delete an entity by id
        /// </summary>
        /// <param name="id">ID of the entity to delete.</param>
        public virtual void DeleteByID(object id)
        {
            // TODO: Optimize DeleteByID so it doesn't fetch the full entity

            var entity = UnitOfWork.GetQuery<TEntity>()
                .Where(KeyCache.GetExpression<TEntity>(id))
                .FirstOrDefault();
            if (entity == null)
                throw new Exception(string.Format("No entity of type {0} found with id {1}", entity.GetType().FullName, id));

            UnitOfWork.Remove<TEntity>(entity);
        }

        /// <summary>
        /// Update an existing entity
        /// </summary>
        /// <param name="entity">The entity to update.</param>
        /// <returns>Reference to the updated entity.</returns>
        public virtual TEntity Update(TEntity entity)
        {
            UnitOfWork.Update<TEntity>(entity);
            return entity;
        }

        /// <summary>
        /// Build a query to fetch entities from the context
        /// </summary>
        /// <returns>IQueryable containing types of TEntity</returns>
        public virtual IQueryable<TEntity> GetQuery()
        {
            return UnitOfWork.GetQuery<TEntity>();
        }

        /// <summary>
        /// Get an entity by its id
        /// </summary>
        /// <param name="id">ID of the entity to get</param>
        /// <returns>Reference to the fetched entity</returns>
        public TEntity GetByID(object id)
        {
            return UnitOfWork.GetQuery<TEntity>()
                .Where(KeyCache.GetExpression<TEntity>(id))
                .FirstOrDefault();
        }

        /// <summary>
        /// Save the unit of work
        /// </summary>
        public void Save()
        {
            if (unitOfWork != null)
                UnitOfWork.Commit();
        }

        /// <summary>
        /// Handle dispose
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Dispose method
        /// </summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    if (unitOfWork != null)
                        unitOfWork.Dispose();
                }

                disposed = true;
            }
        }
    }
}
